package com.example.demo.word;

import lombok.extern.slf4j.Slf4j;
import org.apdplat.word.analysis.TextSimilarity;
import org.apdplat.word.recognition.StopWord;
import org.apdplat.word.segmentation.Segmentation;
import org.apdplat.word.segmentation.SegmentationAlgorithm;
import org.apdplat.word.segmentation.SegmentationFactory;
import org.apdplat.word.segmentation.Word;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigInteger;
import java.util.Collections;
import java.util.List;

/**
 * @author jinyh
 * @date 2021/7/4
 */
@Slf4j
public class SimHashBaseOnWord extends TextSimilarity {
    private static final Logger LOGGER = LoggerFactory.getLogger(SimHashBaseOnWord.class);

    // 生成 64 位的 SimHash
    private int hashBitCount = 64;

    public SimHashBaseOnWord() {
    }

    public SimHashBaseOnWord(int hashBitCount) {
        this.hashBitCount = hashBitCount;
    }

    @Override
    protected double scoreImpl(List<Word> words1, List<Word> words2) {
        //用词频来标注词的权重
        taggingWeightWithWordFrequency(words1, words2);
        //计算SimHash
        String simHash1 = simHash(words1);
        String simHash2 = simHash(words2);
        //计算SimHash值之间的汉明距离
        int hammingDistance = hammingDistance(simHash1, simHash2);
        if (hammingDistance == -1) {
            LOGGER.error("文本1和文本2的SimHash值长度不相等，不能计算汉明距离");
            return 0.0;
        }

        int maxDistance = simHash1.length();
        double score = (1 - hammingDistance / (double) maxDistance);
        return score;
    }

    /**
     * 计算文本对应的 SimHash 值
     *
     * @param text
     * @return
     */
    public String simHash(String text) {
        List<Word> words = seg(text);
        return this.simHash(words);
    }

    /**
     * 计算等长的SimHash值的汉明距离
     * 如不能比较距离（比较的两段文本长度不相等），则返回 64
     *
     * @param simHash1 SimHash值1
     * @param simHash2 SimHash值2
     * @return 汉明距离
     */
    public int hammingDistance(String simHash1, String simHash2) {
        if (simHash1.length() != simHash2.length()) {
            return this.hashBitCount;
        }

        int distance = 0;
        int len = simHash1.length();
        for (int i = 0; i < len; i++) {
            if (simHash1.charAt(i) != simHash2.charAt(i)) {
                distance++;
            }
        }

        return distance;
    }

    /**
     * 计算词列表的SimHash值，通过分词的时候已经统计了词的权重
     *
     * @param words 词列表
     * @return SimHash值
     */
    private String simHash(List<Word> words) {
        float[] hashBit = new float[hashBitCount];
        words.forEach(word -> {
            float weight = word.getWeight() == null ? 1 : word.getWeight();
            BigInteger hash = hash(word.getText());
            for (int i = 0; i < hashBitCount; i++) {
                BigInteger bitMask = new BigInteger("1").shiftLeft(i);
                if (hash.and(bitMask).signum() != 0) {
                    hashBit[i] += weight;
                } else {
                    hashBit[i] -= weight;
                }
            }
        });

        StringBuffer fingerprint = new StringBuffer();
        for (int i = 0; i < hashBitCount; i++) {
            if (hashBit[i] >= 0) {
                fingerprint.append("1");
            } else {
                fingerprint.append("0");
            }
        }

        return fingerprint.toString();
    }

    /**
     * 计算文本的哈希值，很常见的一个 Hash 算法
     *
     * @param word 词
     * @return 哈希值
     */
    private BigInteger hash(String word) {
        if (word == null || word.length() == 0) {
            return new BigInteger("0");
        }

        char[] charArray = word.toCharArray();
        BigInteger x = BigInteger.valueOf(((long) charArray[0]) << 7);
        BigInteger m = new BigInteger("1000003");
        BigInteger mask = new BigInteger("2").pow(hashBitCount).subtract(new BigInteger("1"));
        long sum = 0;
        for (char c : charArray) {
            sum += c;
        }

        x = x.multiply(m).xor(BigInteger.valueOf(sum)).and(mask);
        x = x.xor(new BigInteger(String.valueOf(word.length())));
        if (x.equals(new BigInteger("-1"))) {
            x = new BigInteger("-2");
        }

        return x;
    }

    /**
     * 对文本进行分词
     *
     * @param text
     * @return
     */
    private List<Word> seg(String text) {
        if (text == null) {
            return Collections.emptyList();
        }

        Segmentation segmentation = SegmentationFactory.getSegmentation(SegmentationAlgorithm.MaxNgramScore);
        List<Word> words = segmentation.seg(text);
        if (filterStopWord) {
            //停用词过滤
            StopWord.filterStopWords(words);
        }
        return words;
    }

    public static void main(String[] args) throws Exception {
        String text1 = "我的兴趣爱好是看书";
        String text2 = "看书是我的兴趣爱好";
        String text3 = "我爱好看书";

        SimHashBaseOnWord textSimilarity = new SimHashBaseOnWord();
        double score1pk2 = textSimilarity.similarScore(text1, text2);
//        double score1pk3 = textSimilarity.similarScore(text1, text3);
//        double score2pk3 = textSimilarity.similarScore(text2, text3);

        String sim1 = textSimilarity.simHash("我的兴趣爱好是看书");
        String sim2 = textSimilarity.simHash("看书是我的兴趣爱好");
        System.out.println("我的兴趣爱好是看书" + "和看书是我的兴趣爱好的汉明距离是：" + textSimilarity.hammingDistance(sim1, sim2));
//        System.out.println(text1 + " 和 " + text2 + " 的相似度分值：" + score1pk2);
//        System.out.println(text1 + " 和 " + text3 + " 的相似度分值：" + score1pk3);
//        System.out.println(text2 + " 和 " + text3 + " 的相似度分值：" + score2pk3);
    }
}
